﻿#region COPYRIGHT
//--------------------------------------------------------------------------------
// <copyright file="Widget.cs" company="starLiGHT Entertainment Studios">
//    Copyright (c) 2011 
//       Roland Rosenkranz (Glatzemann@email.de)
// </copyright>
// <license>
//   This file is part of starLiGHT.GUI.
//
//   It is released under the Microsoft Public License (Ms-PL).
//
//   You should have received a copy of the Microsoft Public License
//   along with starLiGHT.GUI. If not, see http://sle.codeplex.com/license.
//    
//   ADDITIONAL (commercial) LICENSES for starLiGHT.GUI are available on request.
// </license>
// <version>
// **************[ starLiGHT.Engine SVN ]**********************
// * $Rev::                       $:  Revision of last commit *
// * $Author::                    $:  Author of last commit   *
// * $Date::                      $:  Date of last commit     *
// ************************************************************
// </version>
//--------------------------------------------------------------------------------
#endregion

namespace starLiGHT.GUI.Widgets
{
    #region Using Statements
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using Microsoft.Xna.Framework;
    using Microsoft.Xna.Framework.Graphics;
    using Microsoft.Xna.Framework.Input;
    using starLiGHT;
    using starLiGHT.GUI;
    #endregion

    public abstract class Widget
    {
        #region Member Variables
        private GuiManager manager;
        private RectangleF displayRectangle;
        private RectangleF clientRectangle;
        private Color? backgroundColor;
        private Color? foregroundColor;
        private Color tint;
        private SpriteFont font;
        private TextAlignment textAlignment;
        private bool visible;
        private string text;
        private Widget parent;
        private string name;
        private bool focused;
        private Layout layout;
        private string currentState;
        private bool border;
        private float zorder;
        private WidgetCollection widgets;
        #endregion

        #region Constructors
        public Widget(GuiManager manager, string name)
            : this(manager, name, null)
        {
        }

        public Widget(GuiManager manager, string name, string layout)
        {
            this.manager = manager;
            this.visible = true;
            this.Name = name;
            this.Text = String.Empty;
            this.font = manager.DefaultFont;
            this.tint = Color.White;
            
            Layout = this.manager.Skin.GetLayout(layout == null ? this.DefaultSkinLayoutName : layout);
        }

        public Widget(GuiManager manager, string name, string text, string layout)
            : this(manager, name, layout)
        {
            this.text = text;
        }

        public Widget(GuiManager manager, string name, string text, string layout, float left, float top, float width, float height)
            : this(manager, name, text, layout)
        {
            this.displayRectangle = new RectangleF(left, top, width, height);
        }

        public Widget(GuiManager manager, string name, string text, float left, float top, float width, float height)
            : this(manager, name)
        {
            this.displayRectangle = new RectangleF(left, top, width, height);
            this.text = text;
        }

        #endregion

        #region Events
        /// <summary>
        /// Occurs when the widget is clicked
        /// </summary>
        public event EventHandler Click;

        /// <summary>
        /// Occurs when the widget is entered.
        /// </summary>
        public event EventHandler Enter;

        /// <summary>
        /// Occurs when the widget receives focus.
        /// </summary>
        public event EventHandler GotFocus;

        /// <summary>
        /// Occurs when the widget loses focus.
        /// </summary>
        public event EventHandler LostFocus;

        /// <summary>
        /// Occurs when the input focus leaves the widget.
        /// </summary>
        public event EventHandler Leave;

        /// <summary>
        /// Occurs when the mouse pointer enters the widget.
        /// </summary>
        public event MouseEventHandler MouseEnter;

        /// <summary>
        /// Occurs when the mouse pointer is moved over the widget.
        /// </summary>
        public event MouseEventHandler MouseMove;

        /// <summary>
        /// Occurs when the mouse pointer is moved.
        /// </summary>
        public event MouseEventHandler GlobalMouseMove;

        /// <summary>
        /// Occurs when the mouse pointer rests on the widget.
        /// </summary>
        /// <remarks>
        /// A typical use of MouseHover is to display a tool tip when the mouse pauses on a widget.
        /// </remarks>
        public event EventHandler MouseHover;

        /// <summary>
        /// Occurs when the mouse pointer is over the widget and a mouse button is pressed.
        /// </summary>
        public event MouseEventHandler MouseDown;

        /// <summary>
        /// Occurs when the mouse wheel moves while the widget has focus.
        /// </summary>
        public event EventHandler MouseWheel;

        /// <summary>
        /// Occurs when the mouse pointer is over the widget and a mouse button is released.
        /// </summary>
        public event MouseEventHandler MouseUp;

        /// <summary>
        /// Occurs when the mouse pointer leaves the widget.
        /// </summary>
        public event EventHandler MouseLeave;

        /// <summary>
        /// Occurs when the widgets location is changed.
        /// </summary>
        public event EventHandler LocationChanged;

        /// <summary>
        /// Occurs when the widgets size is changed.
        /// </summary>
        public event EventHandler SizeChanged;

        /// <summary>
        /// Occurs when the widgets state is changed.
        /// </summary>
        public event EventHandler StateChanged;

        #endregion

        public GuiManager Manager
        {
            get
            {
                return this.manager;
            }
        }

        public WidgetCollection Widgets
        {
            get
            {
                if (this.widgets == null)
                {
                    this.widgets = new WidgetCollection(this);
                }

                return this.widgets;
            }
        }

        public Layout Layout
        {
            get
            {
                return this.layout;
            }

            protected set
            {
                this.layout = value;

                this.SetupDefaultLayout();
            }
        }

        public virtual Widget Parent
        {
            get
            {
                return this.parent;
            }

            set
            {
                if (this.parent != null &&
                    this.parent != value &&
                    this.parent.Widgets.Contains(this))
                {
                    this.widgets.Remove(this);
                }

                this.parent = value;

                if (this.parent != null && !this.parent.Widgets.Contains(this))
                {
                    this.widgets.Add(this);
                }
            }
        }

        public Widget TopMostWidget
        {
            get
            {
                if (this.parent != null)
                {
                    return this.parent.TopMostWidget;
                }
                else
                {
                    return this;
                }
            }
        }

        public Vector2 Location
        {
            get
            {
                return this.displayRectangle.TopLeft;
            }

            set
            {
                if (this.displayRectangle.Location != value)
                {
                    this.displayRectangle.Location = value;
                    this.OnLocationChanged(this, EventArgs.Empty);
                }
            }
        }

        public Vector2 ParentScreenOffset
        {
            get
            {
                float x = 0;
                float y = 0;
                Widget p = this.parent;
                while (p != null)
                {
                    x += p.Location.X;
                    y += p.Location.Y;
                    p = p.parent;
                }

                return new Vector2(x, y);
            }
        }

        public Vector2 ScreenLocation
        {
            get
            {
                return this.Location + this.ParentScreenOffset;
            }
        }

        public Vector2 Size
        {
            get
            {
                return this.displayRectangle.Size;
            }

            set
            {
                if (this.displayRectangle.Size != value)
                {
                    this.displayRectangle.Size = value;
                    this.OnSizeChanged(this, EventArgs.Empty);
                }
            }
        }

        public virtual RectangleF ScissorRectangle
        {
            get
            {
                RectangleF rect = this.displayRectangle;
                rect.Offset(this.ParentScreenOffset);

                return rect;
            }
        }

        public RectangleF ClickRectangle
        {
            get
            {
                RectangleF clickRect = DisplayRectangle;
                foreach (Layer l in this.CurrentState.Layers)
                {
                    clickRect = RectangleF.Union(clickRect, l.ClickMargin.Apply(DisplayRectangle));
                }
                return clickRect;
            }
        }

        public RectangleF DisplayRectangle
        {
            get
            {
                if (this.Parent != null)
                {
                    return new RectangleF(
                                          this.displayRectangle.Left + this.Parent.ClientRectangle.Left,
                                          this.displayRectangle.Top + this.Parent.ClientRectangle.Top,
                                          this.displayRectangle.Width,
                                          this.displayRectangle.Height);
                }
                else
                {
                    return this.displayRectangle;
                }
            }

            set
            {
                if (this.displayRectangle != value)
                {
                    this.displayRectangle = value;
                }
            }
        }

        public virtual RectangleF ClientRectangle
        {
            get
            {
                Layer layer = null;
                foreach (Layer l in this.CurrentState.Layers)
                {
                    layer = l;
                    break;
                }

                RectangleF clientRectangle = this.DisplayRectangle;
                if (layer != null)
                {
                    clientRectangle.X += layer.ContentMargin.Left;
                    clientRectangle.Y += layer.ContentMargin.Top;
                    clientRectangle.BottomRight -= new Vector2(layer.ContentMargin.Right, layer.ContentMargin.Bottom);
                }

                return clientRectangle;
            }
        }

        public Color ForeColor
        {
            get
            {
                if (this.foregroundColor.HasValue)
                {
                    return this.foregroundColor.Value;
                }
                else
                {
                    return Layout.DefaultForeColor;
                }
            }

            set
            {
                if (this.foregroundColor != value)
                {
                    this.foregroundColor = value;
                }
            }
        }

        public Color BackColor
        {
            get
            {
                if (this.backgroundColor.HasValue)
                {
                    return this.backgroundColor.Value;
                }
                else
                {
                    return Layout.DefaultBackColor;
                }
            }

            set
            {
                if (this.backgroundColor != value)
                {
                    this.backgroundColor = value;
                }
            }
        }

        public Color Tint
        {
            get
            {
                return tint;
            }
            set
            {
                tint = value;
            }
        }

        public string Name
        {
            get
            {
                return this.name;
            }

            set
            {
                this.name = value;
            }
        }

        public virtual string Text
        {
            get
            {
                return this.text;
            }

            set
            {
                this.text = value;
            }
        }

        public bool Visible
        {
            get
            {
                return this.visible;
            }

            set
            {
                this.visible = value;
                this.Focused = false;
            }
        }

        public SpriteFont Font
        {
            get
            {
                return this.font;
            }

            set
            {
                this.font = value;
            }
        }

        public TextAlignment TextAlignment
        {
            get
            {
                return this.textAlignment;
            }

            set
            {
                this.textAlignment = value;
            }
        }

        public virtual bool Focused
        {
            get
            {
                return this.focused;
            }

            set
            {
                if (this.focused == value)
                {
                    return;
                }

                this.focused = value && this.CanFocus;

                if (this.focused == true)
                {
                    if (this.Parent != null && this.Parent is ContainerWidget)
                    {
                        foreach (Widget w in ((ContainerWidget)this.Parent).Widgets)
                        {
                            if (!w.Equals(this))
                            {
                                w.Focused = false;
                            }
                        }

                        ((ContainerWidget)this.Parent).ActiveWidget = this;
                    }
                    else
                    {
                        foreach (Widget w in this.manager.Widgets)
                        {
                            if (!w.Equals(this))
                            {
                                w.Focused = false;
                            }
                        }

                        this.manager.ActiveWidget = this;
                    }

                    this.BringToFront(this.Parent as ContainerWidget);
                    this.OnGotFocus(this, EventArgs.Empty);
                }
                else
                {
                    if (this.Parent != null && this.Parent is ContainerWidget)
                    {
                        ContainerWidget p = this.Parent as ContainerWidget;
                        p.UnfocusChild();
                        if (p.ActiveWidget == this)
                        {
                            p.ActiveWidget = null;
                        }
                    }
                    else
                    {
                        if (this.manager.ActiveWidget == this)
                        {
                            this.manager.ActiveWidget = null;
                        }
                    }

                    this.OnLostFocus(this, EventArgs.Empty);
                }
            }
        }

        public virtual float ZOrder
        {
            get
            {
                return this.zorder;
            }

            set
            {
                if (this.zorder != value)
                {
                    this.zorder = value;
                }
            }
        }

        public State CurrentState
        {
            get
            {
                return Layout.GetState(this.currentState);
            }
        }

        public abstract bool CanFocus
        {
            get;
        }

        public virtual string DefaultSkinLayoutName
        {
            get
            {
                return this.GetType().Name;
            }
        }

        protected bool Border
        {
            get
            {
                return this.border;
            }

            set
            {
                this.border = value;
            }
        }

        public Window FindWindow()
        {
            if (this is Window)
            {
                return this as Window;
            }
            else if (this.Parent != null)
            {
                if (this.Parent is Window)
                {
                    return this.Parent as Window;
                }
                else
                {
                    return this.Parent.FindWindow();
                }
            }
            else
            {
                return null;
            }
        }

        public void SetFocus()
        {
            this.Focused = true;
        }

        public virtual void OnClick(object sender, System.EventArgs e)
        {
            if (this.Click != null)
            {
                this.Click(sender, e);
            }
        }

        public virtual void OnEnter(object sender, System.EventArgs e)
        {
            if (this.Enter != null)
            {
                this.Enter(sender, e);
            }
        }

        public virtual void OnGotFocus(object sender, System.EventArgs e)
        {
            if (this.GotFocus != null)
            {
                this.GotFocus(sender, e);
            }
        }

        public virtual void OnLostFocus(object sender, EventArgs e)
        {
            if (this.LostFocus != null)
            {
                this.LostFocus(sender, e);
            }
        }

        public virtual void OnLeave(object sender, System.EventArgs e)
        {
            if (this.Leave != null)
            {
                this.Leave(sender, e);
            }
        }

        public virtual void OnMouseEnter(object sender, MouseEventArgs e)
        {
            if (this.MouseEnter != null)
            {
                this.MouseEnter(sender, e);
            }
        }

        public virtual void OnMouseMove(object sender, MouseEventArgs e)
        {
            if (this.MouseMove != null)
            {
                this.MouseMove(sender, e);
            }
        }

        public virtual void OnGlobalMouseMove(object sender, MouseEventArgs e)
        {
            if (this.GlobalMouseMove != null)
            {
                this.GlobalMouseMove(sender, e);
            }
        }

        public virtual void OnMouseHover(object sender, System.EventArgs e)
        {
            if (this.MouseHover != null)
            {
                this.MouseHover(sender, e);
            }
        }

        public virtual void OnMouseDown(object sender, MouseEventArgs e)
        {
            if (this.MouseDown != null)
            {
                this.MouseDown(sender, e);
            }
        }

        public virtual void OnMouseWheel(object sender, System.EventArgs e)
        {
            if (this.MouseWheel != null)
            {
                this.MouseWheel(sender, e);
            }
        }

        public virtual void OnMouseUp(object sender, MouseEventArgs e)
        {
            if (this.MouseUp != null)
            {
                this.MouseUp(sender, e);
            }

            if (e.LeftButton)
            {
                this.OnClick(sender, e);
            }
        }

        public virtual void OnMouseLeave(object sender, System.EventArgs e)
        {
            if (this.MouseLeave != null)
            {
                this.MouseLeave(sender, e);
            }
        }

        public virtual void OnLocationChanged(object sender, System.EventArgs e)
        {
            if (this.LocationChanged != null)
            {
                this.LocationChanged(sender, e);
            }
        }

        public virtual void OnSizeChanged(object sender, System.EventArgs e)
        {
            if (this.SizeChanged != null)
            {
                this.SizeChanged(sender, e);
            }
        }

        public virtual void OnStateChanged(object sender, System.EventArgs e)
        {

            if (this.StateChanged != null)
            {
                this.StateChanged(sender, e);
            }
        }

        public void BringToFront(ContainerWidget parent)
        {
            if (parent == null)
            {
                this.manager.BringToFront(this);
            }
            else
            {
                this.manager.BringToFront(parent, this);
            }
        }

        public void SendToBack()
        {
            this.manager.SendToBack(this);
        }

        public virtual void Draw(GameTime gameTime, IGuiRenderer renderer)
        {
            if (this.visible && this.CurrentState != null)
            {
                foreach (Layer layer in this.CurrentState.Layers)
                {
                    renderer.DrawLayer(this, layer);

                    foreach (TextLabel te in layer.TextLabels)
                    {
                        this.DrawText(renderer, layer, te);
                    }
                }
            }
        }

        public virtual void DrawText(IGuiRenderer renderer, Layer layer, TextLabel te)
        {
        }

        public Vector2 GetPosition(RectangleF displayRectangle, RectangleF clientRectangle, Margin margin, Alignment alignment)
        {
            Vector2 position = Vector2.Zero;

            switch (alignment)
            {
                case Alignment.TopRight:
                    position.X = displayRectangle.Right - (margin.Right + clientRectangle.Width);
                    position.Y = clientRectangle.Y + margin.Top;
                    break;
                case Alignment.Right:
                    position.X = displayRectangle.Right - clientRectangle.Width + margin.Right;
                    position.Y = clientRectangle.Y + margin.Top;
                    break;
                case Alignment.Bottom:
                    position.X = clientRectangle.X + margin.Left;
                    position.Y = displayRectangle.Bottom - (clientRectangle.Height + margin.Bottom);
                    break;
                case Alignment.Top:
                case Alignment.TopLeft:
                default:
                    position.X = clientRectangle.X + margin.Left;
                    position.Y = clientRectangle.Y + margin.Top;
                    break;
            }

            return position;
        }

        public virtual bool HitTest(Vector2 position)
        {
            return this.ClickRectangle.X <= position.X &&
                   position.X <= this.ClickRectangle.Right &&
                   this.ClickRectangle.Top <= position.Y &&
                   position.Y <= this.ClickRectangle.Bottom; // why not ClickRectangle.Contains() ?
        }

        public virtual bool HitTest(float x, float y)
        {
            return this.ClickRectangle.X <= x &&
                   x <= this.ClickRectangle.Right &&
                   this.ClickRectangle.Top <= y &&
                   y <= this.ClickRectangle.Bottom;
        }

        public void SetState(string name)
        {
            this.currentState = name;

            State s = Layout.GetState(name);
            if (s.ForeColor.HasValue)
            {
                this.ForeColor = s.ForeColor.Value;
            }
            else
            {
                this.ForeColor = Layout.DefaultForeColor;
            }

            if (s.BackColor.HasValue)
            {
                this.BackColor = s.BackColor.Value;
            }
            else
            {
                this.BackColor = Layout.DefaultBackColor;
            }

            OnStateChanged(this, new EventArgs());
        }

        internal Vector2 GetTextPosition(string text, TextAlignment alignment, int lineNumber, float verticalOffset)
        {
            Vector2 topLeft = this.ClientRectangle.TopLeft;
            switch (alignment)
            {
                case TextAlignment.Center:
                    {
                        Vector2 txtSize = this.Font.MeasureString(text);
                        topLeft.X += (0.5f * this.ClientRectangle.Width) - (0.5f * txtSize.X);
                    }

                    break;
                case TextAlignment.Right:
                    {
                        Vector2 txtSize = this.Font.MeasureString(text);
                        topLeft.X += this.ClientRectangle.Width - txtSize.X;
                    }

                    break;
                default:
                    break;
            }

            topLeft.Y += (lineNumber * this.Font.LineSpacing) + verticalOffset;

            return topLeft;
        }

        private void SetupDefaultLayout()
        {
            if (Layout == null)
            {
                throw new Exception("Unable to setup default layout. Layout is null. Looks like the problem is, that no default Layout defined for '" + this.DefaultSkinLayoutName + "' in skin file.");
            }

            this.Size = Layout.DefaultSize;
            this.BackColor = Layout.DefaultBackColor;
            this.ForeColor = Layout.DefaultForeColor;
        }

        public override string ToString()
        {
            string className = base.ToString().Split('.').Last();

            return string.Format("\"{0}\" ({1})", Name, className);
        }
    }
}
